【第1234期】前端布局基础概述
前言
在刚刚过去的第四届CSS大会上,不少人提到了布局的问题。那么这篇很适合重温一下。今日早读文章由@蔡剑涛投稿分享。
@蔡剑涛,来自海致星图前端团队,专注前端项目架构设计、组件库开发、开发流程精进和团队技术体系建设
正文从这开始~
背景
前端圈有个“梗”:在面试时,问个css的position属性能刷掉一半人,其中不乏工作四五年的同学。在公司一直有参与前端的基础面试,深感这个“梗”不是个玩笑。
然而,我觉得实际比例可能会更高,甚至很多面试官自己也未必真正掌握。因为大部分前端同学,可能不知道初始包含块的概念,或知道但对这个概念理解有误。
造成这种现象的原因主要有两方面,一方面是在介绍这个知识点时,网上有谬误的文章太多,国内外亦如此(MDN也名列其中),导致很多同学被误导(我一开始也是),而且这种错误被代代相传;另一方面可能是我们平时不太注重概念的定义、自身对待知识的态度还不够严谨、缺乏验证精神和系统总结的习惯。
一次偶然的机会,我发现了这种谬误,并找到了W3C组织对初始化包含块的官方定义,也为了让刚入前端圈的同学少走一些弯路,在此我想借本文分享给大家(详述请见5.5. 包含块章节),也系统分享一下,本人在前端布局基础方面积累的浅薄经验。(因为是系统概述,所以篇幅会比较长,希望各位读者有心理准备)
什么是前端布局基础?
前端布局方案主要有三种:
传统布局方案(借助浮动、定位等手段)
flex布局方案
grid布局方案
这些方案都能够解决布局问题,而且每个方案都有各自的理论基础,那么哪一个方案的基础理论可以称得上是前端布局基础?要回答这个问题,我们还得深入去了解这三种方案的特性。
传统布局方案,需要使用者熟练掌握元素的分类及布局特性、浮动原理和定位原理等众多基础知识,方能在解决各类前端布局问题时游刃有余,这不仅学习成本大,而且实现的复杂度也高,实现的CSS代码也不够精简、优雅。但由于其基础知识来源于CSS2,所以浏览器兼容性最好,对于用户是友好的。
flex布局方案,正是为了解决传统布局方案的种种不便,而提出的一种新型改进方案,它不再需要借助浮动和定位等布局手段,而是通过父元素(flex box)单方面配置相关的CSS属性来决定子元素的布局规则,且在大多情况下无需子元素(flex item)参与,就能完成子元素间的布局问题,不仅学习成本低(公司之前有几个后端工程师亦能快速上手),且大大简化了布局的实现复杂度,CSS代码也更加精炼。美中不足的是IE10才开始支持,且需要使用-ms-前缀(IE11无需)。
虽然现今的手机多使用的是现代浏览器,对flex支持度较好,然而并不是每一款手机都如此:笔者曾在一个移动端项目采用过flex布局方案,然而公司的测试同学在“华为荣耀5”的自带浏览器,检测到无法支持flex布局,我们能够跟测试的同学说,是这款华为手机的浏览器有问题吗?显然不能。于是故笔者在项目早期就及时放弃了flex布局方案,改用传统布局方案实现,避免了后面大规模的改动。
grid布局方案,是由微软提出,相对于传统布局方案和flex布局方案,它是一种二维布局方案,在IE10开始支持,但需要使用-ms-后缀(IE11+不再需要)。
总的来说,这三类方案都能基本解决日常的前端布局问题,且从易用性、灵活性和强大性来说,flex布局和grid布局更是未来的趋势。但是从当前各版本浏览器在用户市场上的使用情况和各方案的浏览器兼容性来看,传统布局方案对用户最友好,具有一定的不可替代性,所以我觉得,传统布局方案是最应该先掌握好的,尤其是对于在to B企业工作的前端同学来说。
所以本文将详细介绍的“前端布局基础”,指的是围绕着“传统布局方案”的众多CSS知识,其主要内容来源于CSS2规范。
为什么要学好前端布局基础?
页面写多了的前端同学,我想应该都会有这样一个深刻的感受:在编写页面时,经常会遇到不同场景的布局问题,我们不仅需要针对特定的场景选定可实现的布局实现方案,而且需要考虑未来可能发生的变化。
而要做好这一点,就需要扎实的前端基础作为依托。
所以在我看来,学好前端布局基础,其目的是为了在面对不同场景的布局问题时,能够提出一种合理的布局方案:既能解决问题,又能最大程度地拥抱变化。
量化布局方案的合理性
前面提到过的“解决问题”、“拥抱变化”,仅仅是合理布局方案的两大核心目标,如果想要让目标更好地落地,我们仍需要一些量化合理性的原则,来提升对目标的方向感,以让目标变得更加可执行。
说到量化“解决问题”这个目标,对于即写即呈现的前端代码来说,我们可以很直观地判断一种方案是否可行,所以不需要太多的量化手段,我们主要是要量化“拥抱变化”这个目标。
要想量化“拥抱变化”这个目标,我们首先得清楚“变化”有哪些。笔者根据过往的开发经验,将变化分为两大类:一是布局需求的变化,二是运行环境的变化。
而针这这两类变化,我提出如下量化原则:
一、对于布局需求的变化,可以做到:
方便快速定位需修改的位置
能够不花或用最少的修改成本应对变化
二、对于运行环境的变化,可以做到:
在不同浏览器均有正确或良好的显示
如果一个方案能够体现以上几点原则,我认为可以称得上是一个合理的方案。最后,我将布局实现方案的合理性归纳为:方案在满足正确性的前提下,其实现逻辑规范、实现职责分明且拥有良好的浏览器兼容性。
下面我们正式开始介绍与“传统布局方案”相关的布局基础知识。
布局基础要点
CSS标准盒模型(或W3C盒模型)
一个web页面是由众多html元素拼凑而成的,而每一个html元素,都被解析为一个矩形盒,而CSS盒模型就是这种矩形盒的解构模型。CSS盒模型,它由内到外、被四条边界Content edge、Padding edge、Border edge和Margin edge划分为四个区域:Content area、Padding area、Border area和Margin area,在形状上,Content area(又称content-box)是实心矩形,其余是空心环形(空心部分是Content area),如下图所示:
此外,每个区域都有其特定的作用:Content area,是当前元素用来容纳所有子孙元素;Padding area,是当前元素用来隔离自身和子孙元素;Border area是当前元素用来显示自身的轮廓;Margin area,是当前元素用来隔离自身和相邻元素。理解每个区域的作用和职责至关重要,有助于我们写出优雅、清晰的布局代码。
而每个区域的尺寸,又分别由特定的CSS属性来控制,如下图所示:
这些CSS尺寸属性(width、height、padding、border和margin),相当于一个个hook,我们可以通过设置这些“hook”来达到调整元素尺寸的目的。
box-sizing(CSS3属性)
1. box-sizing的作用
box-sizing,顾名思义,其作用与设置CSS box的尺寸大小有关,而CSS box又可细分为:
content-box(即content area)
padding-box(=content area + padding area)
border-box(=content area + padding area + border area)
margin-box(=content area + padding area + border area + margin area)
简单来说,box-sizing的作用就是告诉浏览器:CSS属性width和height是用于设置哪一种box的尺寸,在W3C标准中,box-sizing的值仅有content-box和border-box(firefox则额外支持padding-box)。所以,
当box-sizing的值为content-box(默认值)时,有:
width = content-width;
height = content-height;
当box-sizing的值为border-box时,有:
width = content-width + padding-left + padding-right + border-left-width + border-right-width;
height = content-height + padding-top + padding-bottom + border-top-height + border-bottom-height;
关于box-sizing的作用,还有另一种表述:告诉浏览器,是使用W3C盒模型,还是使用IE盒模型。
2. box-sizing的浏览器兼容性
box-sizing是CSS3属性,在IE8+(包含IE8)开始支持,然而在IE8,box-sizing的值为border-box时,不能与min-width, max-width, min-height或max-height的一起使用,因为IE8对min-和max-的解析,仍是作用于content-box,不受box-sizing属性控制。
3. box-sizing的产生原因
仅仅掌握box-sizing的基础使用,是无法真正理解box-sizing的作用,所以要想把box-sizing用好,我们还得从CSS盒模型的发展史来深入理解box-sizing的产生原因。
在CSS的发展历程中,有两个版本,一个是IE盒模型,另外一个是W3C盒模型。IE盒模型,在IE5-(包含IE5)和navigator4上均有使用;而W3C盒模型,在IE6+(包含IE6)标准模式开始得到支持。两种版本的盒模型,其实在模型结构上是一致的,只是with和height属性的计算规则不一样,其区别,等价于“box-sizing的两个属性值border-box和content-box的区别“,如下图所示:
在了解了CSS盒模型的发展历程,以及后来新增的box-sizing的开始支持时间,我们不难发现:
IE5-采用IE盒模型
IE6、7的标准模式放弃了IE盒模型,转为使用W3C盒模型
IE8+借助box-sizing,又重新提供了对IE盒模型的支持
对于IE盒模型,我们看到了W3C组织先去后留的反复态度,我不禁提出以下两点疑惑:
问题一:为什么W3C组织在制定盒模型标准时,一开始会放弃IE盒模型,而重新建立以content-box为计算规则的W3C盒模型?W3C盒模型比IE盒模型好在哪里?
问题二:为什么在CSS3中,又重新提供了对IE盒模型的支持(box-sizing设置为border-box),又是基于哪方面的考虑?
关于第一个问题,本人并没有找到相关的官方说明,但我比较认可的一种说法是:
在日常生活中,我们在放东西时,会关心东西放到多大的盒子里面,这里的“多大”,往往指的是盒子的容量,而不是整个盒子的尺寸。而HTML元素也被看成是一个盒子、一个容器,相应地,我们也会更关注其内容区域的尺寸,也更希望对内容区域有更强的控制力。所以,从存储的角度来看,W3C盒模型更符合这种认知,借助width和height,我们可以通过声明的方式,直接设置conent-box的尺寸。而如果采用IE盒模型,我们只能先设置整个盒子的尺寸(border-box),最后由浏览器自动计算出content-box的尺寸,显得对content-box尺寸的控制力较弱。
关于第二个问题,我认为有以下几个原因:
有助于复用基于IE盒模型开发的CSS代码;
IE盒模型的“遗老遗少”可以延续计算习惯;
部分html元素,在解析时依然采用IE盒模型的计算规则(这样的元素有select、button),使用IE盒模型有助于保持一致性;
从元素布局的角度来看,IE盒模型的width和height的语义更符合人类的直观认知(盒子的尺寸、轮廓应该以border为界);
在弹性布局和响应式布局场景,IE盒模型比W3C盒模型表现更佳(更容易实现、浏览器兼容性更好),如设置某个元素的宽度始终占当前行总宽度的固定百分比(小于100%),并且该元素拥有固定像素的padding;
举个例子:设置一个元素,其宽度分别为当前行的40%,且该元素的padding固定为10px。
IE盒模型的实现方案:
方案一: 使用一个div即可实现,直接设置width为40%,padding为10px;
W3C盒模型的实现方案:
方案一:使用两个div模拟实现,外层div的width设置为40%,内层div的padding为10px, width为auto;
方案二:使用一个div即可实现,但是需要借用CSS3的calc函数,动态计算其内容区域的宽度,即width为calc(40% - 20px), padding为10px;
显然,IE盒模型的实现方案更加简洁,而且浏览器兼容性更好。
对上述两个问题的解答,其实也是对IE盒模型和W3C盒模型的一个比较。我们可以从比较中,明晰两种盒模型各自的优缺点。同时,经过大量的实践经验证明和充分讨论,IE盒模型总体上是优于W3C盒模型,这也是IE盒模型能够“王者归来”,被W3C组织重新启用的真正原因。
于是乎,为了重新在新规范中支持IE盒模型,也为了向后兼容W3C盒模型,W3C组织在CSS3中添加了box-sizing属性,用于切换这两种盒模型。
4. 对box-sizing的评价
在我看来,在CSS3中添加box-sizing其实是一种比较trick的弥补方式。虽然这种设计能重新提供对IE盒模型的支持,但是在某种程度上,造成了CSS属性width和height具有二义性,使其职责变得不单一。然而这似乎又是最可取的修正方案了,因为在网上已经存在了大量基于W3C盒模型开发的网页,后续的修正方案不得不考虑向后兼容。我们只能在不合理设计的基础上,再次用不优雅的设计来解决新的问题。
如果能够穿越时空,回到W3C组织在讨论“如何设计标准盒模型”时,我认为更合适的设计方案是添加新的属性单独用于设置content-box的尺寸,而保留IE盒模型width和height原来的语义。这样就不会有后来的box-sizing属性。
我猜想W3C组织也想过这种方案,但是当时可能认为:
直接设置元素border-box尺寸的意义不大,且border-box的尺寸设置也能够通过设置content-box的尺寸来实现;(其实同时两种支持content-box和border-box尺寸的设置也无妨,完全可以当做是语法糖)
设置content-box尺寸又属于高频操作,若新增的属性命名为content-width或content-height则显得名称太长;(命名为cwidth和cheight也行)
基于这两点,最终提出了用width和height来设置content-box尺寸的解决方案,也就是如今我们看到的W3C盒模型。
纵观CSS盒模型的发展史和box-sizing的创建原因,感触比较深的就是:不合理的设计并不是总会被修正,因为既有实现的广泛应用,会使得其被继续遵循。而后续的新增设计,也是建立在先前不合理设计的基础上。这是否也验证了黑格尔的哲学名言:存在即合理?
关于对box-sizing的评价和思考,可能显得有一些马后炮,一些猜想也可能只是笔者的凭空臆想,并非W3C组织原意。在这里只是为了分享我对重构的一些思考,也是为了与和我有同样疑惑的同学做个交流。
5. box-sizing的最佳实践
在这里主要回答三个问题:
问题一:box-sizing的值,取content-box好,还是取border-box值好?
如果最低需要兼容IE6、7,那么box-sizing不可使用,只能使用W3C盒模型;
如果最低只需兼容IE8,那么使用content-box在功能上完全没有问题,只是在一些弹性布局和响应式布局实现上,会稍微麻烦一点;而border-box虽然在这些方面表现更好,但是不能和IE8的min-width、min-height、max-width和max-height四个属性一同使用,使用的话就要稍微注意一下;
如果最低只需兼容IE9,那么本人觉得,全局配置取content-box更为合适,局部配置二者均可。原因如下:
CSS3提供了calc函数(IE9+),使得W3C盒模型有了强有力的助攻,在弹性布局和响应式布局的表现,与IE盒模型无异;
默认优于配置原则:我个人认为,“默认优于配置”,特别是在reset.css这种架构级、平台级的配置文件,要尽量避免对未来可能引入的模块有侵入性。譬如,我们在一个项目中时常需要引入第三方组件,如果这个组件没有强声明box-sizing,那么其默认使用的就是W3C标准盒模型,如果在全局的reset.css中设置box-sizing的值为border-box以选用IE盒模型,那么就会影响到这一类默认基于W3C盒模型的第三方组件的样式。这里也给我们提了一个醒,在封装组件时,记得强声明box-sizing,哪怕你使用默认的content-box。
总之,大部分场景二者可以互换,只是使用理念不一样。小部分场景border-box更具优势,但随着calc函数的支持,这种优势已经不再,相反content-box是默认值的优势愈加明显。
我个人建议是:全局使用默认W3C盒模型(你的CSS代码最低能够兼容IE6/7,在IE8也可以和min-和max-一起使用),局部场景二者均可(仅把IE盒模型当作是一种布局技巧来使用)。你喜欢全局使用IE盒模型也是可以的,只要确认项目只需要兼容到IE8,即便有可能影响到引入的第三方组件,也是有办法处理的。
问题二:如果想要全局使用IE盒模型,那么在reset.css中,该怎样设置box-sizing?
这里提供一个参考:
html {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
*, *:before, *:after {
-webkit-box-sizing: inherit;
-moz-box-sizing: inherit;
box-sizing: inherit;
}
这样设置的好处有:
子元素的盒模型类型,默认由父元素决定,方便组件统一设置;
支持低版本的浏览器:Safari (< 5.1), Chrome (< 10), and Firefox (< 29);
问题三:Bootstrap3开始,全局使用IE盒模型(box-sizing取border-box),又是基于怎样的考虑?怎么协调好与基于标准盒模型开发的第三方组件的关系?
众所周知,BS2还考虑对IE7的兼容,而BS3彻底放弃了对IE7的兼容,并将box-sizing设置为border-box。关于这一点,可见“Bootstrap 3 released”官方发布的change list,摘录如下:
从以上直白的表述中:Better box model by default(默认使用更好的盒模型),我们可以看出BS作者是IE盒模型的拥趸。作者也把理由罗列了出来,其核心内容也是如前面所提到的,IE盒模型在响应式布局上的良好表现。补充的一点是,如果不全局设置border-box,而每个组件及其子组件单独设置,维护起来将是个梦魇(作者在官方编号为12351的issure中有提到)。
而关于BS如何处理好与基于标准盒模型开发的第三方组件的关系,亦可参见编号为12351的issue:”Move away from * {box-sizing: border-box } to play nice with 3rd party scripts”
作者在issue中,霸气又委婉地回应:
BS并不考虑对第三方组件和框架的支持。作者委婉地说,BS是一个大项目,活跃维护者也主要是四个人,顾不来所有人的需求啊~(但感觉作者是在说,BS是个又大又全的框架,你丫还搞第三方组件干嘛呀)
2.IE盒模型,用了大家都说好,为什么第三方组件不转过来支持IE盒模型啊(果然是铁粉)
本章节从box-sizing的作用、浏览器兼容性、产生原因、评价和最佳实践这5个切入点,来讲解box-sizing属性,以期加深各位同学对这个属性的理解和掌握。特别要强调的一点是,如果刚接手某个项目,在编写CSS代码前,先看看项目是否有全局配置box-sizing,并根据具体的取值来选用相应的尺寸计算规则。
元素的分类及其布局特性
1. 元素的分类
从元素的布局特性来分,主要可以分为三类元素:block-level(块级)元素、inline-level(行内级)元素和inline-block-level(行内块级)元素,我们可以对其下个定义:
1.1. 块级元素
display属性取block、table、flex、grid和list-item等值的独占一行显示的元素。
1.2. 行内级元素
display属性取inline值的可在同一行内排列显示的元素。
1.3. 行内块级元素
display属性取inline-block、inline-table、inline-flex和inline-grid等值的兼具块级元素和行内级元素布局特性的元素。
友情提示:
1)关于各类元素display的取值,实际已全部罗列,但为了保证定义能拥抱变化(未来可能引入新的display属性值),在罗列时使用了等字;
2)w3c官方文档,把display属性值为inline、inline-block、inline-table的元素,统称为inline-level元素,我不太喜欢也不太认可这种泛泛的分类,本文重新定义了一个“inline-block-level元素”的概念,来对“inline-level元素”进行了细分,并将inline-blocks、inline-tables单独分类为inline-block-level元素,原文档如下:“The following values of the ‘display’ property make an element inline-level: ‘inline’, ‘inline-table’, and ‘inline-block’.”
2. 元素的布局特性
2.1. 块级元素(block-level)的布局特性
对于块级元素,有如下几个布局特性:
独占一行(width默认为100%,height为0);
可以设置任何尺寸相关的属性(width、padding、margin和border);
2.2. 行内级元素(inline-level)的布局特性
在讲行内级元素的布局特性之前,我们先了解一下行内级元素的分类,其可再细分两类元素:
1)可置换行内元素
在MDN中,其对“可置换行内元素”的定义如下:
按字面翻译,“可置换行内元素”,是展示内容不在CSS作用域内的元素。这句话是不是不好理解?我们可以换另外一种方式理解:“可置换行内元素”,是这样一类元素,其展示的内容是通过元素的src、value等属性或CSS content属性从外部引用得到的,可被替换的。随着内容来源或内容数量的变化,可置换元素本身也会有水平和垂直方向上尺寸的变化。典型的可替换元素有 <img>
、 <object>
、 <video>
和 <embed>
,表单类的可替换元素有<textarea>
和<input>
,某些元素只在一些特殊情况下表现为可替换元素,例如 <audio>
、<object>
、<canvas>
和<applet>
。
特别地,通过 CSS content 属性来插入的对象又被称作 匿名可置换元素。
2)不可置换行内元素
不可置换行内元素其实就是我们常见的一类行内元素,这一类行内元素有<a>
和<span>
等。不可置换行内元素是相对于“可置换行内元素的,其展示的内容是在CSS作用域范围内的,是不可替换的。
言归正传,行内级元素有如下几个布局特性:
在一行内可以与多个同类型的元素按从左到右的顺序排列;
不可置换行内元素不能设置width、height和垂直方向上的margin,而可置换行内元素则可以;
在水平和垂直方向上的对齐方式,行内级元素分别受父元素的text-align属性和自身vertical-align属性的控制(父元素是table-cell元素时,也受父元素的vertical-align属性控制),在水平方向上默认左对齐,在垂直方向上默认在行框的baseline基线上显示(“行框”的概念,会在后面深入讲解);
友情提示:
1)有时候我们不必太纠结于哪些行内元素是可置换行内元素,因为有些浏览器(如chrome)的默认样式(user agent stylesheet),会将这一类元素重置为inline-block元素,我们可以统一把可置换行内元素理解为inline-block元素,因为其布局特性与inline-block-level元素相同。
2)当inline-level元素水平排列时,两两之间可能会出现大约6px的空白,这是由元素间的空白字符(换行符、空格或制表符)产生,如下图所示:
清除方法有很多,本人习惯用浮动的方式来处理,其它方法可自行google。
2.3. 行内块级元素(inline-block-level)的布局特性
行内块级元素兼具block-level元素和inline-level元素的布局特性,主要体现为:
排列方式与行内级元素同,不独占一行,在一行内按从左到右的顺序排列;
水平和垂直方向上的对齐方式与行内级元素同;
和块级元素一样,可以设置任何尺寸属性(但width默认为0);
注:我们不难发现,其实可置换行内元素,其布局特性与inline-block-level元素相同。
格式化上下文(Formatting Context)
格式化上下文,它指的是具有某种CSS格式化规则(布局规则)的上下文环境,在这个上下文环境内的所有子元素,都将根据其特定的CSS格式化规则来进行排列。
我们可以给某个作为容器的元素指定特定的格式化上下文,也就是说我们可以定义一个具有特定布局规则的渲染区域。常见的格式化上下文有BFC(CSS2.1 规范)、IFC(CSS2.1 规范)、 FFC(CSS3规范新增)和GFC(CSS3规范新增),具体介绍如下:
1. BFC
1.1. 定义
BFC, 全称是block formatting context,它是一个独立封闭的渲染区域,在这个区域内的所有元素,从区域的顶部起,一个接一个地根据自身的布局特性进行排列:在这个区域内的块级元素 ,按从上到下的顺序显示,相邻的块级元素可以使用margin隔离,但在垂直方向上相邻的块级元素会发生margin合并;在这个区域内的inline-level或inline-level-block元素,则按从左到右的顺序显示(W3C组织说BFC内部的元素都是一个接一个地垂直显示,我觉得不是很严格,因为BFC内部也可以容纳inline-level和inline-level-block元素,所以这里我的解释和W3C还是稍微有一些不一样)。具有BFC格式化环境的元素,我们称之为BFC元素,可以说,BFC定义了BFC元素content区域的渲染规则。
看到这段描述,是不是觉得BFC的渲染规则,不就是文档流的默认布局规则吗?确实很像,但不完全等同。BFC元素内部的渲染规则和普通块级元素内部的渲染规则,还是有一些不同的,我们将在5.4.1.3. 特性一节详述。
1.2. 创建方式
创建BFC元素的方式有如下几种(摘自MDN BFC):
根元素或其它包含它的元素
浮动元素 (元素的
float
不是none
)绝对定位元素 (元素的
position
为absolute
或fixed
)内联块 (元素具有
display``: inline-block
)表格单元格 (元素具有
display``: table-cell,HTML表格单元格默认属性
)表格标题 (元素具有
display``: table-caption
, HTML表格标题默认属性)overflow
值不为visible
的块元素display``: flow-root
contain
为以下值的元素:layout
,content
, 或strict
弹性项 (
display``: flex
或inline-flex
元素的子元素)网格项 (
display``: grid
或inline-grid
元素的子元素)多列容器 (元素的
column-count
或column-width
不为auto
, 包括column-count: 1
的元素)column-span``: all
应当总是会创建一个新的格式化上下文,即便具有column-span: all
的元素并不被包裹在一个多列容器中。
1.3. 特性
BFC元素具有如下特性:
对应一个独立、封闭的渲染区域,子元素的CSS样式不会影响BFC元素外部;
举个例子,我们分别用连续的两个块级元素,一个是普通块级元素,另一个是BFC元素(均使用绿色背景),分别包裹一个margin-top为20px的子元素(黄色背景),对比其布局效果:
说明:
普通块级元素,其子元素的margin-top,不会隔开自身与父元素(普通块级元素),但是会作用到父元素外部(将父元素和叔伯元素或祖父元素隔开);
BFC元素,作为一个独立、封闭的渲染区域,其子元素的margin-top,则会隔开自身与父元素(BFC元素),而不会影响到父元素外部;
浮动子元素参与BFC父元素的高度计算,也就是BFC元素能够识别浮动元素(将元素声明为BFC元素,也是clearfix解决父元素塌陷问题的一种常用方法);
举个例子:
说明:
BFC元素,能够识别浮动子元素,浮动子元素参与BFC元素的高度计算,不会出现“高度塌陷”问题;
普通块级元素,不能够识别浮动子元素,会出现“高度塌陷”问题;
占据文档流的BFC元素(可使用overflow: auto创建),能够识别浮动的兄弟元素;
举个例子:
说明:
普通块级元素,不能够识别浮动的兄弟元素,会被浮动的兄弟元素覆盖部分内容;
占据文档流的BFC元素(可使用overflow: auto创建),能够识别浮动的兄弟元素,不会被浮动的兄弟元素覆盖,与之同行显示;
占据文档流的BFC元素(可使用overflow: auto创建),width为auto时,会占满当前行的剩余宽度;
举个例子:
说明:
文档流中的BFC元素, width为auto时,会占满当前行的剩余宽度;
2. IFC
2.1. 定义
IFC, 全称是inline formatting context,其内部的元素,在水平方向上,一个接一个地显示;在垂直方向上,每个元素可以设置不同的对齐方式;IFC内部的元素,被一行行的矩形框所包含,这些虚拟的矩形框,我们称为行框(line box)。IFC的作用区域,可以看成是包含其所有子元素的行框组成的矩形区域。
2.2. 创建方式
和BFC相比,它的创建方式是被动的、隐式的,是由所包含的子元素来创建:只有在一个区域内仅包含可水平排列的元素时才会生成,这些子元素可以是文本、inline-level元素或inline-block-level元素。
2.3. 特性
IFC内部的元素,按从左到右、从上到下的顺序排布;
IFC内部的每个元素,都可以通过设置vertical-align属性,来调整在垂直方向上的对齐;
包含这些内部元素的矩形区域,形成的每一行,被称为line box(行框,后面会详细介绍);
3. FFC和GFC
FFC(flex formatting context)和GFC(grid formatting context),分别是flex布局和grid布局的内容,这两个模块的内容非本文介绍的重点,所以感兴趣的同学可以自行google。
包含块(Containing Block)
1. 定义
我们在设置元素尺寸属性(width、height、padding、margin和border)的百分比值或偏移属性(top、right、bottom和left)的值时,通常会有一个“相对参考系”,这个”相对参考系”一般是包裹着这个元素的块级祖先元素(一般是块级父元素)或离这个元素最近的非static(relative、absolute和fixed)定位的祖先元素。这些具有“相对参考系”作用的祖先元素,其容纳区域(cotent box或padding box),其实还有一个专门术语形容之,那就是包含块(在知识体系中有个包含块的概念,有助于加深对position定位原理的掌握)。
特别地,relative定位元素,其尺寸属性(width、height等)的“相对坐标系”仍是其包含块(块级祖先元素(一般是父元素)的content box),但是偏移属性(top、right、bottom和left)的“相对坐标系”则是其在文档流原来的位置。
2. ICB(initial containing block, 初始包含块)
2.1. 定义
如前面所说,任何一个元素都会有一个包含块作为设置尺寸属性和偏移属性的“相对参考系”,而对于顶层的根元素,没有任何元素包裹它,它的包含块是什么?它选取什么作为“相对参考系”?
其实根元素是有包含块的,它是一个不可见的矩形框,W3C组织称之为ICB(initial containing block, 初始包含块)。以下是W3C组织对ICB对定义:
The containing block in which the root element lives is a rectangle called the initial containing block.
5.5.2.2. ICB的尺寸和起始位置(左上角坐标)
在解释ICB的尺寸和起始位置时,在这里先简单补充一个背景知识:连续媒体(continuous media)和分页媒体(paged media)。如何理解这两个概念?在视觉阅读层面,它们是展示内容的两种呈现方式。
连续媒体,就是采用连续展示内容的方式,它保持了展示内容显示的连续性(一页显示所有内容),我们可以在连续媒体的viewport(可视窗口)查看当前呈现的内容。特别地,浏览器窗口就可以看成是连续媒体,当内容的尺寸超过viewport时,读者可以通过平滑滚动的方式来阅读内容。
分页媒体,就是采用切页展示内容的方式,它将要展示的内容切分为等尺寸的多页(分页显示所有内容),我们可以在分页媒体的page area(页面显示区域)查看当前呈现的内容。特别地,像幻灯片、电子书阅读器,就可以看成是分页媒体,当内容的尺寸超过page area时,读者可以通过切页的方式来阅读内容;
对于属于连续媒体(continuous media)的浏览器窗口来说,ICB的尺寸为viewport(浏览器视窗),其起始位置为画布原点(canvas origin,即首屏的左上角,浏览器渲染数据后生成的内容文档可以看成是一张画布)。
对于分页媒体来说,ICB的尺寸为page area(关于ICB在分页媒体的起始位置,没有找到相关资料,但这个对于本文来说也不是重点)。
直观来看,根元素的包含块ICB,就是“首屏”。
3. 不同定位元素分别对应的包含块
static和relative定位元素的包含块,为其块级祖先元素(通常是块级父元素)的content box;
absolute定位元素的包含块,为最近的非静态定位祖先元素的padding box,查无非静态定位祖先元素,那么它的包含块是ICB(即根元素的包含块);
fix定位元素的包含块,为当前viewport(视窗);
在这里要强调的一点,ICB(初始包含块)是专有名词,它特指根元素的包含块。不要将一个元素的初始包含块,错误理解为它的父元素。MDN的一位编辑者也犯了这种错误。具体如下:
经修正后:
也有一些权威CSS书籍说,当一个绝对定位元素找不到最近的非static祖先元素时,则相对于根元素定位,这种说法也是不严谨的。刚好看到一本,如下:
我们可以通过一个简单的例子推翻这种说法:将根元素html的高度设置为超过viewport高度,如5000px(假设viewport高度为500px),再将一个没有最近的非static祖先元素的绝对定位元素的bottom设置为0,尺寸为100px100px即可。如果真如该书中所言,那么在首屏时,该绝对定位元素是被隐藏在滚动条下面的。而实际情况是:该绝对定位元素必然出现在首屏的底端,并且会随着页面滚动而滚动。验证如下:
相信这个谬误在前端圈流传已久,希望各位同学引起重视。
基本原理
1. 文档流(正常流)
1.1. 定义
关于“文档流”,并没有找到较为官方的定义。笔者从google搜到一些认为比较靠谱的解释,罗列如下:
The document flow is the model by which elements are rendered by default in the CSS specifications. In this model, elements are rendered according by their default display rule. In other words, block-level elements are displayed on a new line and inline elements on the same line. Everything is stacked in an ordered way from top to bottom. 摘自:《CSS: understanding the document flow》
Document flow is the arrangement of page elements as defined by positioning statements and the order of html statements; that is, how the different elements take up space and arrange themselves around each other.摘自:《What is “document flow”?》
在这里我想分享一下我自己对“文档流”下的定义:
文档流,是页面元素默认存放的“容器”。
1.2. 特性
文档流具有如下特性:
文档流按照页面元素书写的顺序,将页面元素按从左到右,从上至下的一般顺序进行排列,而页面元素则根据自身的布局属性(block-box or inline-box),决定是行内显示,还是换行显示;
文档流内的元素,相互尊重:有序排列,彼此识别;
1.3. 脱离文档流
元素脱离文档流,按我之前下的定义,其实就意味着:元素脱离了默认存放的容器,换到另外一个容器存放。一个元素脱离了文档流,这样会导致:其父元素无法识别其,其也不参与父元素高度的计算。若有一个父元素的所有子元素都脱离文档流,则会出现“高度塌陷”问题。常见的脱离文档流的方法有:
将元素设置为浮动元素
将元素设置为absolute、fixed元素
2. 浮动(float属性)
2.1. 浮动元素的分类
根据float属性的设置,元素可以分为浮动元素(值为left或right)和非浮动元素(值为none)。而按浮动方向划分,又可细分为:
左浮动元素:float值为left的元素
右浮动元素:float值为right的元素
2.2. 浮动原理
要想掌握浮动元素的浮动原理,只要理解浮动元素的浮动起始位置、浮动方向和浮动结束位置即可。
浮动起始位置
浮动元素(包括左右)的浮动起始位置,为最后一行最左侧的空白位置,而不管空白位置是否能够容纳当前浮动元素;
浮动方向
左浮动元素的浮动方向为从起始位置向左浮动;
右浮动元素的浮动方向为从起始位置向右浮动;
浮动结束位置
左浮动元素遇到第一个左浮动元素或包含块的最左侧padding时,结束浮动;
右浮动元素遇到第一个右浮动元素或包含块的最右侧padding时,结束浮动;
以下demo可以帮助各位同学理解浮动元素的三要素:
/06:layout/float/1. 浮动元素三要素.html:
<body>
<div class="fl">左浮动元素-1(width: 30%; height: 100px;)</div>
<div class="fl">左浮动元素-2(width: 30%; height: 200px;)</div>
<div class="fl">左浮动元素-3(width: 30%; height: 100px;)</div>
<div class="fl">左浮动元素-4(width: 30%; height: 100px;)</div>
</body>
显示结果:
说明:
a. 有四个连续左浮动的元素,每个元素宽度为30%;
b. 当一行排满三个元素时,当前行只剩10%的宽度,不足以容纳第四个左浮动元素;
c. 第四个浮动元素,从起始位置(最后一行的最左侧空白)开始向左浮动,直到遇到第二个浮动元素的边界;
为了帮助大家理解好浮动原理,在这里我想额外定义几个术语:
左浮动队:由若干个连续的左浮动元素组成
右浮动队:由若干个连续的右浮动元素组成
左浮动队头元素:左浮动队的第一个元素,也是最左侧的元素
右浮动队头元素:右浮动队的第一个元素,也是最右侧的元素
特别地,
同一行内,最多有两条浮动队,一是左浮动队,二是右浮动队;
同一行内,一条浮动队可能占满一行;
连续浮动的若干元素,如果无法在同一行内显示,则会按行被切分为两条或更多条浮动队;
5.6.2.3. 浮动对元素display的影响
当元素设置为浮动元素后,可能会引发display属性的值变化,具体规则如下:
3. 清除浮动(clear属性)
3.1. 三要素
清除浮动,其作用是改变“当前元素”与“前一个声明的浮动元素”之间的默认布局规则,这种改变主要体现为:让当前元素换行显示。这句话包含三个要素,分别为:
使用者:当前元素(浮动元素或者非浮动元素的块级元素)
作用对象(清除谁的浮动):前一个声明的浮动元素
目的(作用):让当前元素换行显示
特别地,为什么使用者不包括非浮动的inline元素?因为非浮动的inline元素能够识别浮动元素,是否使用clear清除“前一个声明的浮动元素”的浮动,其布局结果是一样的。感兴趣的同学可以参考:
06:layout/clear/4.非浮动inline元素清除左浮动.html,
可以在调试器中观察注释非浮动inline元素的clear:left前后,其显示位置的变化。而非浮动的块级元素,因为无法识别前面声明的左浮动元素,故会和左浮动元素发生重叠(左浮动元素在上),所以非浮动的块级元素使用clear:left清除前一个左浮动元素,就能避免重叠的现象。
3.2. clear属性的取值及应用场景
前面简单介绍了clear属性的作用,是清除前面声明的浮动元素的浮动,然后让当前元素换行显示。但是要具体怎么使用,我们还得深入到clear的属性值和应用场景。
clear属性的取值有left、right和both。那么它们的应用场景分别是什么?
left值的应用场景是,前面声明的浮动元素是向左浮动(float: left);
right的应用场景是,前面声明的浮动元素是向右浮动(float: right);
both的应用场景是,前面声明的浮动元素的浮动方向不确定,可能是左,也可能是右(了解过clearfix实现原理的同学,就不难明白);
再次强调一下,当前元素如果要清除浮动,清除的是前面声明的浮动元素的浮动,其clear属性要取什么值,跟当前元素的是否是浮动元素或浮动方向没有任何关系,而取决于其前面声明的浮动元素的浮动方向。
举个例子,一个右浮动元素(float:right),前面有一个左浮动元素(float:left),如果这个右浮动元素使用clear: left时,这个元素会清除前一个元素的浮动,进而换行显示;如果使用clear:left时,这个元素在当前行的最右端显示。如下图所示
06:layout/clear/4.右浮动清除左浮动.html:
在了解完clear属性的取值和应用场景,我们可以对其作用,可以总结为:
如果当前元素浮动元素或非浮动的块级元素,且前面声明的元素是左(右)浮动元素,那么当前元素可以使用clear: left(clear: right),清除前一个左(右)浮动元素的左(右)浮动,此时当前元素会换行显示;如果当前元素clear的浮动与前面一个浮动元素的浮动方向不同向,当前元素不会换行;
3.3. 清除浮动后的margin合并问题
1)两个浮动元素之间,其垂直方向上的margin不会发生合并,如下图所示:
2)非浮动的块级元素和浮动元素之间,其垂直方向上的margin会发生合并,如下图所示:
特别地,MDN的文档说非浮动的块级元素与浮动元素间不会发生margin合并,实际上会,上述结果已经证明,已在MDN上更正该错误。以下为MDN未修改前的原话:
3.4. 清除浮动的特殊应用:解决父元素高度塌陷问题
众所周知,当一个父元素里面的所有元素都是浮动元素时,此时父元素无法识别这些浮动子元素,会进一步导致父元素发生高度塌陷问题。一种通用的解决方案就是在父元素内部的尾部append一个非浮动的、尺寸为0的块级元素(后面简称fix元素),然后使用clear:both,让这个fix元素换行显示,进而让父元素能够识别前一行的高度。这种朴素的方案其实就是clearfix的基本原理,clearfix只是更加优雅地用:after来实现fix元素。
特别说明:解决父元素高度塌陷问题,还可以通过将父元素声明为BFC元素来实现。
4. 定位(position属性)
4.1. 定位元素的分类
根据position属性的取值,static(默认值)、relative、absolute、fixed,元素可以分为静态定位元素(值为static)、相对定位元素(值为relative)、绝对定位元素(值为absoute)和固定定位元素(值为fixed)。
注:position的取值还有sticky,但IE11都不支持,此处不讲
4.2. 定位原理
static定位元素定位时的相对坐标系:无法设置top、right、bottom和left这四个偏移属性;
relative定位元素定位时的相对坐标系:元素在文档流原来的位置(区域);
absolute定位元素定位时的相对坐标系:离元素最近的一个非static(包含relative、absolute和fixed)定位祖先元素(包含块为其padding box),如果没有则为ICB(初始包含块),即根元素html的包含块;
fixed定位元素定位时的相对坐标系:当前的视窗(viewport);
5. line box(行框)
5.1. 定义
前面在介绍IFC时,我们提到过line box的定义:包含IFC内部的所有子元素的虚拟矩形区域,形成的每一行,称为line box。由于它是矩形的,中文常见将之翻译为行框。
5.2. 模型结构(七线谱)
line box的模型结构,形如七线谱,其中有六条重要的线:top线、text-top线、middle线、baseline线、text-bottom线和bottom线,如下图所示:
其中top线到text-top线的区域和bottom线到text-bottom的区域,又称为行半距(half-leading),两个行半距之和,为一个行距;text-top线到text-bottom线的区域,称之为内容区域(content-area)。如下图所示:
5.3. 行框高度的计算
行框的高度,即一行的top线和bottom线间的垂直距离,这个垂直距离为:上下两个行半距的高度和一个内容区域的高度之和。影响行框高度计算的因素来自两方面,一是自身line-height属性的设置,二是内部inline-level子元素的margin box高度的取值和line-height、vertical-align两个属性的设置。关于其计算规则,具体罗列如下:
一个元素的行框高度,可由该元素的line-height属性设置;
一个元素的行框高度,受不可置换(span、a、label等)的子元素的内容高度(text-top到text-bottom的垂直距离)影响(内容高度又受font-size属性和浏览器的解析规则影响,但主要由font-size决定;相同的font-size,在不同的浏览器,计算出来的内容高度也不一样,最终导致的行框高度也不一样);
一个元素的行框高度,可由不可置换(span、a、label等)的子元素的line-height属性设置;
一个元素的行框高度,可由可置换行内元素(如img)或display属性为inline-block、inline-table的这一类inline-block-level子元素的margin box高度和vertical-align属性决定,当vertical-align为top或bottom时,行框的高度达到最小,刚好为子元素的margin box高度;
如果同时满足以上设置条件,那么行框的高度取最大值;
友情提示:在图1img元素的margin box高度比行框高度小,我们会看到img元素到父元素的底端会有一段空白,为什么会有这种现象?张鑫旭老师在《CSS深入理解vertical-align和line-height的基友关系》一文中将之定义为“幽灵空白节点”,其实结合行框理论来解释,这段空白并不“幽灵”,也很好理解:它是行框的baseline线到bottom线的垂直距离,可置行内换元素如img和inline-block-level元素,在被浏览器解析时,会和“文本”一样,默认在baseline线上显示,而不是在行框的bottom线上。
举个例子: 行框高度的计算
html:
<style>
.line-box {
background: yellow;
line-height: 32px;
font-size: 20px;
}
.span-1 {
line-height: 40px;
background: red;
}
.span-2 {
line-height: 38px;
background: green;
}
img {
width: 50px;
height: 50px;
}
</style>
<body>
<div class="line-box">
<span class="span-1">span(line-height: 40px)</span>
<span class="span-2">span(line-height: 38px)</span>
</div>
<div class="line-box">
<span class="span-1">span(line-height: 40px)</span>
<span class="span-2">span(line-height: 38px)</span>
<img src="#" />
</div>
</body>
显示结果(chrome下):
图1. line box内部仅有不可置换元素
说明:
a. 元素每一行的line-height,既可以由当前元素的line-height属性设置(32px),也可以由该行子元素的line-height属性设置(分别是40px和38px),但取最大的line-height(40px),如图1所示;
b. 特别地,如果一行内还有可以设置height的可置换元素如img(height: 50px),且img的高度大于设置的最大line-height(40px)时,那么该行会被撑高,浏览器会重新计算line-height(最终结果为63px),如图2所示;
5.4. 与line box行框有关的两个重要属性:line-height和vertical-align
相信很多前端同学有这样的感觉:line-height和vertical-align这两个属性总是形影不离,而且有着一种说不清的关系。
它们到底有什么联系吗?
其实这两个属性的关系可由行框和行框内的inline-level元素来体现。line-height属性决定inline-level元素所在行框的高度,它是inline-level元素在一行内垂直方向上的显示范围;vertical-align属性则决定inline-level元素在一行内的垂直对齐方式,即决定inline-level元素在一行内垂直方向上的最终位置。下面我们来深入介绍这两个属性:
1)line-height属性
1.1)line-height属性的作用
line-height属性一般用于块级元素设置其内部每一行的高度,即默认行高;line-height属性也可以用于不可置换元素(如span、a)设置所在行框的高度。也就说,每一行计算出来的最终行高,既受父元素line-height属性的影响,也受子元素line-height属性的影响。
1.2)line-height属性的取值
line-height的取值有
表示使用指定带单位的长度来设置line-height,这些长度单位可以是px、pt和em和rem; 表示用font-size值的倍数来设置line-height; 表示用font-size值的百分比来设置line-height; 而关键字normal,其最终计算出来的尺寸,则取决于浏览器各自的解析机制和选用的font-family类型:浏览器会根据选用的font-family类型来计算出一个合适的值,W3C官方推荐使用
值,并且推荐值的范围为1.0到1.2之间(但经过实测,浏览器在实现时,远比这个复杂,而且不同浏览器间也存在差异。唯一可以确定的一点是,最终的行高肯定会比font-size值要大)。
我们在将UI稿实现为页面代码时,常常强调要Pixel Perfect、高精准地还原设计稿。但
我们常常会遇到这样一个问题:当我们用一个块级元素包裹文本时,会发现块级元素的高度,实际比文本的font-size尺寸还要高,导致上下形成了一些空白,进一步造成块级元素内的文本与垂直方向上相邻元素的距离变大,如下图所示:
这种误差是由于line-height的默认值为normal,那有什么办法可以解决这个问题呢?较常用的方法是将块级元素的line-height设置为1或100%。设置后的结果如下图所示:
这样做也有一点不好,那就是:浏览器最终解析出来的内容高度,有可能是比font-size要大的,当行高为font-size时,文本内容就会溢出。我们将字体放大为100px,溢出效果就很明显,如下图所示:x31
1.2)line-height属性对元素高度的影响
我们可以通过了解line-height属性分别对块元素和不可置换的行内元素自身高度的影响、以及不可置换的子元素的line-height属性对父元素高度的影响,来深入理解line-height属性的作用。
为了帮助大家更好地理解line-height,我设计了如下三个小demo:
demo1: line-height属性对块级元素自身height的影响
html:
<body>
// div为单行
<div class="block">
div(line-height: 32px)
</div>
// div为多行
<div class="block">
div(line-height: 32px)
<br>
div(line-height: 32px)
</div>
</body>
显示结果(chrome下):
说明:
a. 当一个块元素不设置height,而且这个块元素仅有一行时,那么其height刚好等于line-height;
b. 当一个块元素不设置height,而且这个块元素有多行时,那么其height刚好等于每一行的line-height之和;
demo2: line-height属性对不可置换行内元素(如span)的height的影响
html:
<div class="line-box">
<span class="inline-element">
span(line-height: 40px;font-size: 20px)
</span>
</div>
显示结果:
说明:
a. 不可置换行内元素为单行时,其height等于text-top线到text-bottom线的距离,所以line-height的取值不会影响到其height,其height由font-size和浏览器的默认解析机制决定(一般>font-size,大多少则取决于浏览器解析机制,如图1、2所示);
b. 不可置换元素为多行时,其height等于第一行的text-top线到最后一行的text-bottom线的距离,此时line-height的取值就会影响到其height,其height=line-height 行数 - (line-height - 每一行text-top线到text-bottom的距离),即height=line-height 行数 - 2 * half-heading;如下图所示:
demo3:不可置换的子元素(如span)的line-height,对父元素height的影响
html:
<div class="line-box">
<span class="span-1">span(line-height: 40px)</span>
<span class="span-2">span(line-height: 38px)</span>
<br>
<span class="span-3">span(line-height: 50px)</span>
</div>
显示结果(chrome下):
说明:
a. 块级元素每一行的行高都可以不同;
b. 不可置换的行内子元素的line-height属性,可以决定所在行框的高度;
c. 如果一个父元素不设置height,那么其height为所有行的高度之和;
d. 不可置换的行内子元素的line-height属性,是通过影响行框的高度来影响父元素的高度的。
2)vertical-align属性
vertical-align的作用之一:就是用于设置inline-level元素自身在“行框”内的垂直对齐方式,其控制范围在一行内。较常用的值有top、middle、baseline(默认值)和bottom,不常用的有text-top、text-bottom、sub和super,几乎不用的有
vertical-align属性的几个重要取值的作用如下:
当vertical-align取top时,表示当前inline-level元素的上margin edge在行框内贴顶;
当vertical-align取bottom时,表示当前inline-level元素的下margin edge在行框内贴底;
当vertical-align取middle时,表示当前inline-level元素的垂直平分线和行框的middle线重合;
当vertical-align取baseline时,表示当前inline-level元素的下margin edge紧贴在行框的baseline上;
vertical-align属性的另一个作用:就是table-cell元素用于控制其内部子元素在垂直方向上的对齐方式,而且这些子元素的类型不受限制,不仅可以是span,而且可以是div。
举个例子:
html:
<table>
<tr>
<td class="top">div(top)</td>
<td class="middle">div(middle)</td>
<td class="baseline">div(baseline)</td>
<td class="bottom">div(bottom)</td>
</tr>
</table>
显示结果:
说明:
a. table-cell元素通过设置自身的vertical-align属性,来设置其子元素在垂直方向上的对齐方式;
特别说明:我们常用说的使用table布局来实现子元素在父元素内部垂直居中,就是运用到了这个知识点。
6. margin
在传统的布局方案中,margin不仅用来隔离自身与相邻元素或父元素(一般不推荐用来隔离父元素),而且在元素水平和垂直方向上的居中定位,亦发挥了重要的作用。下面我们来深入介绍margin的相关布局特性。
6.1. auto的计算规则(在width和margin上使用)
1)水平方向上
谈到“如何设置文档流中的块级元素在父元素内部水平居中?”这个布局问题,相信很多同学马上会想到这个方案:给元素设置固定宽度,并使用margin: 0 auto(水平方向上的margin为auto)
.child{
width:100px;
margin:0 auto;
}
它的实现原理,包含如下四个基础知识点:
块级元素的水平尺寸(outerWidth,margin box的宽度)的计算规则:
outerWidth = margin-left + border-left-width + padding-left + width + padding-right + border-right + margin-right,
如下图所示:
文档流中的块级元素,其在水平方向上的尺寸属性的初始值,仅width为auto,其余为0
在水平方向上的尺寸属性,仅width、margin-left和margin-right可以设置auto值(自动计算)
文档流中的块级元素,其在水平方向上的尺寸属性,当值为auto时,表示取所在行的剩余宽度,特别地,当margin-left和margin-right的值均为auto时,会平分所在行的剩余宽度
在理解了上述四个基础知识点,我们不难理解其原理:
当块级元素在水平方向上的尺寸属性,除了margin-left和margin-right值为auto,其余皆为定值,那么margin-left和margin-right会自动平分父元素的剩余宽度,进而达到在父元素内部水平居中的效果,如下图所示:
结合上述四个基础知识点,我们还可以得出如下结论:
文档流中的块级元素如果不设置任何水平尺寸属性,那么其默认的width为当前行的content width,此时width取auto和100%,最终的计算值一样
2)垂直方向上
或许我们都曾问过这样的一个问题:既然可以通过设置margin: 0 auto,让文档流中的块级元素在父元素内部水平居中,那么可否通过设置margin: auto 0,让其垂直居中?
答案是不能的,因为文档流中的块级元素,其垂直方向上的margin为auto时的计算规则和在水平方向上的计算规则不同:不取父元素剩余的高度,而为0。W3C标准原话如下:
“If “margin-top” or “margin-bottom” is “auto”, their used value is 0″
或许大家会问,为什么要这样设计呢?官方并没有给出说明,但是有网友给出了如下几个解释,罗列如下,供各位参考(可以在留言中分享你的看法,本人比较认同第一条):
It could be because of the typical vertical page flow, where page size >increases height-wise. So, centering an element vertically in its container is not going to make it appear centered, relative to the page itself, unlike when it’s done horizontally (in most cases).
And maybe it’s because of this same reason, they decided to add an exception for absolute elements which can be centered vertically along the entire page’s height.
It could also be because of the margin collapse effect (a collapse of adjacent elements” margins) which is another exception for the vertical margins.
在W3C标准规约中,虽不能使用margin: auto 0,实现普通文档流中的块级元素在父元素内部垂直居中,但是可以使用margin: auto 0,实现绝对或固定定位元素在包含块内部垂直居中,因为绝对或固定定位元素垂直方向上的margin,其 auto仍会取包含块的剩余高度,W3C官方文档给出的计算公式如下:
'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom' = height of containing block
等价的简化公式:
子元素outerHeight = 包含块height - 子元素top - 子元素bottom
提示:
子元素outerHeight,是指当前子元素margin box的高度;
包含块height,可以为当前子元素的相对定位参考系元素的padding box的高度、ICB的高度或viewport的高度;
要使用上述规则来实现子元素在父元素内部垂直居中,那么就需要保证:
子元素的top值 + bottom值为0(原因:让子元素outerHeight 等于包含块height)
子元素的top值取0(原因:让子元素的上margin edge紧贴包含块的顶部)
下面通过一个demo来详细介绍:
html:
<style>
.parent{
position:relative;
background:yellow;
height:100px;
}
.child{
position:absolute;
top:0;
bottom:0;
background:green;
width:140px;
height:20px;
margin:auto 0;
text-align:center;
}
</style>
<body>
<div class="parent">
<div class="child">垂直居中的子元素</div>
</div>
</body>
显示结果:
说明:
a. 绝对定位的子元素的top为0,其轮廓(包含margin)的上边界与其包含块内容区域的顶部紧贴;
b. 由已知求未知:包含块的height已知,子元素的top值和bottom值之和为0,即子元素的outerHeight可求,又因为子元素height已知,故垂直方向上的剩余高度可以确定,当子元素的margin-top和margin-bottom均为auto时,将平分剩余的高度;
6.2. margin合并(margin collapsing)
在垂直方向上,元素与自身或相邻的兄弟元素、父元素、子元素的margin,会发生合并(注意:在IE6/7子元素垂直方向上的margin会隔离父元素,而不是和父元素的margin发生合并,IE8+则与标准浏览器同),margin取较大的值,而在水平方向上则不会。各位读者可以从下面三个demo,来理解垂直方向上margin的合并:
1) 父元素与子元素(第一个子元素、最后一个子元素)
html:
<div class="wrapper">
<div class="parent">
<div class="child first-child">第一个子元素(margin-top: 20px)</div>
<div class="child last-child">最后一个子元素(margin-bottom: 20px)</div>
</div>
</div>
显示结果:
说明:
a. 父元素(黄色)的margin-top(40px)和第一个子元素的margin-top(20px)发生融合(取较大的40px);
b. 父元素(黄色)的margin-bottom(40px)和最后一个子元素的margin-bottom(20px)发生融合(取较大的40px);
2) 上下相邻的兄弟元素(同层元素)
html:
<div class="parent">
<div class="child first-child">第一个元素(margin-bottom: 40px)</div>
<div class="child last-child">第二个元素(margin-top: 20px)</div>
</div>
显示结果:
说明:
a. 第一个元素的margin-bottom(40px)和第二个元素的margin-top(20px)发生融合(取较大的40px);
3) 空块级元素
html:
<div class="line">第一行</div>
<div class="empty-block"></div>
<div class="line">第二行</div>
显示结果:
说明:
a. 两行之间的空白区域,为一个空块元素;
b. 空块的margin-top为40px, margin-bottom为20px;
c. 两行之间的距离为40px,可知空块元素的margin-top和margin-bottom发生了合并,取较大值;
这里我们举了三个会在垂直方向上发生margin合并的例子,但是细心的同学可能记得,我们在“5.6.3.3. 清除浮动后的margin合并问题”章节,举了一个在垂直方向上例子不会发生margin合并的例子:浮动元素间在垂直方向上不会发生margin合并。
6.3. 子元素的margin隔离父元素
细心的读者不难发现,在“2) 上下相邻的兄弟元素(同层元素)” 的demo可以看到
子元素(绿色)垂直方向上的margin并没有将自己与父元素(黄色)隔离开(IE6/7会,IE8+不会)。
那么什么情况,子元素的margin可以和父元素隔离开?
首先要强调的一点是, 子元素水平方向上的margin,始终能够隔离父元素;然而子元素在垂直方向上的margin隔离父元素的情况,本人记录的仅有以下四种(欢迎补充):
case 1: 父元素是BFC元素
html:
<div class="parent">
<div class="child">子元素(margin: 20px)</div>
</div>
显示结果:
说明:
a. 父元素(黄色)是BFC元素,子元素(绿色)垂直方向上的margin能够隔离父元素;
case 2:父元素拥有border
html:
<div class="parent">
<div class="child">子元素(margin: 20px)</div>
</div>
显示结果:
说明:
a. 父元素(黄色)拥有border,子元素(绿色)垂直方向上的margin能够隔离父元素;
case 3:父元素拥有padding
html:
<div class="parent">
<div class="child">子元素(margin: 20px)</div>
</div>
显示结果:
说明:
a. 父元素(黄色)拥有padding,子元素(绿色)垂直方向上的margin能够隔离父元素;
case 4:子元素是可置换元素或display为inline-block、inline-table、table-caption的元素
html:
<style>
.parent{
background:yellow;
width:100%;
height:60px;
line-height:20px;
}
.inline-block{
display:inline-block;
}
.inline-table{
display:inline-table;
}
img,.inline-block,.inline-table{
border:1px solid green;
height:20px;
min-width:20px;
margin-top:10px;
vertical-align:top;
}
</style>
<body>
<div class="parent">
<img src="frame_image.svg" />
<div class="inline-block">display: inline-block </div>
<div class="inline-table">display: inline-table</div>
</div>
</body>
显示结果:
说明:
a. 可置换行内的和display属性为inline-block、inline-table的子元素,其垂直方向上的margin能够隔离自身与父元素;
在这里对margin合并和margin隔离作一个小结,本人把遇到过的所有在垂直方向上会发生与不会发生margin合并、能使用margin隔离与不能使用margin隔离的例子,都罗列了出来(然而这仅仅是在标准浏览器的例子,在IE6/7情况还会不一样,但因为现在基本无需再兼容低版本的IE,所以就不再列举)。目的不是让大家记住它,而是让大家避开它:在垂直方向上,兄弟元素间尽量不要设置相邻的margin,子元素也不要使用margin来隔离父元素,这样能尽量保证你的CSS代码,在各种版本的浏览器都有较好的兼容性(显示一致)。
结尾语
本文从CSS盒模型及其发展史、元素的分类及其布局特性、格式化上下文(Formatting Context)、包含块、基本原理(文档流、浮动、清除浮动、定位、行框、margin)这五大模块,系统介绍了一下前端的布局基础,希望此次分享,能够让各位读者对前端基础布局有一个底层、体系的认识。因为内容涵盖过广,难免会有纰漏,还望见谅和指正。
此篇文章断断续续写了几个月,从年前写到年后,一方面是因为这个标题太大,含括的内容太多,需要慢慢梳理;一方面是文中要讲的东西,很多是出于本人的感悟和总结,为了保证观点的正确性、严谨性以及和行业的标准术语做好同步,需逐一验证;还有一方面也是近几个月来,本人需要处理的私事较多,分散了精力。
最后我还想再分享一些心得体会:
不要轻视简单的东西
我们在生活中总是容易忽略一些简单的东西,因为轻视简单,导致过了几年依然也没有掌握,前端的同学更应该注意这一点。
尽信书不如无书
不要太相信权威,而是要学会验证、总结,并构建自己的知识体系。
学技术要看官方文档
很多同学在初学时喜欢看一些快速入门的教程,我觉得这种学习习惯挺好的,但是建议不要遗漏官方文档的学习。因为初学者很难去鉴定一个非官方文档的质量,运气不好的话,还会被误导。而且官方文档最贴近原作者的想法,我们更容易体会到其设计思想。
演示代码地址
https://github.com/momopig/simplicity/tree/master/06:layout
最后,为你推荐
关于本文
作者:@蔡剑涛
原文:https://mp.weixin.qq.com/s/-LcNZWFFty2lWuND6uuNNA